﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ActiveAwareCommand.cs" company="Concept Architecture">
//   (c) 2010 Muhammad Umer Farooq
// </copyright>
// <summary>
//   A command that tracks active aware components and routes execution to the active component.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Hisaab.Windows.Common
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;

    /// <summary>
    /// A command that tracks active aware components and routes execution to the active component.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This command can send execution to one of many active aware components. An active aware component is any component that implements the
    /// <see cref="IActiveAware"/> interface. When an active aware component activates, it becomes the target of the <c>ActiveAwareCommand</c>, at least until
    /// some other active aware component is activated.
    /// </para>
    /// <para>
    /// Potential targets are registered via the RegisterPotentialTarget method and unregistered via the <see cref="UnregisterPotentialTarget"/> method.
    /// Once a potential target is registered, the <c>ActiveAwareCommand</c> will listen for changes to the target's active state and update the
    /// <see cref="CurrentTarget"/> accordingly.
    /// </para>
    /// </remarks>
    public class ActiveAwareCommand : Command
    {
        /// <summary>
        /// Lock on active command
        /// </summary>
        private readonly object _lock;

        /// <summary>
        /// Active aware command handler
        /// </summary>
        private readonly IList<ActiveAwareHandlers> _activeAwareHandlers;

        /// <summary>
        /// Active aware target
        /// </summary>
        private IActiveAware _currentTarget;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveAwareCommand"/> class.
        /// </summary>
        public ActiveAwareCommand()
        {
            _lock = new object();
            _activeAwareHandlers = new List<ActiveAwareHandlers>();
        }

        /// <summary>
        /// Occurs whenever the <see cref="CurrentTarget"/> property changes.
        /// </summary>
        public event EventHandler<EventArgs> CurrentTargetChanged;

        /// <summary>
        /// Gets the current target, which may be <see langword="null"/> if there is no current target.
        /// </summary>
        public IActiveAware CurrentTarget
        {
            get
            {
                return _currentTarget;
            }

            private set
            {
                lock (_lock)
                {
                    if (_currentTarget != value)
                    {
                        _currentTarget = value;
                        OnCurrentTargetChanged();
                        OnCanExecuteChanged();
                    }
                }
            }
        }

        /// <summary>
        /// Registers a potential target with this command. If the target is active, the command is always considered able to execute.
        /// </summary>
        /// <param name="target">
        /// The potential target.
        /// </param>
        /// <param name="execute">
        /// A delegate to execute when the command is executed against the target.
        /// </param>
        public void RegisterPotentialTarget(IActiveAware target, Action<object> execute)
        {
            RegisterPotentialTarget(target, execute, null);
        }

        /// <summary>
        /// Registers a potential target with this command.
        /// </summary>
        /// <param name="target">
        /// The potential target.
        /// </param>
        /// <param name="execute">
        /// A delegate to execute when the command is executed against the target.
        /// </param>
        /// <param name="canExecute">
        /// A delegate to execute when determining whether the command can execute against the target.
        /// </param>
        public void RegisterPotentialTarget(IActiveAware target, Action<object> execute, Predicate<object> canExecute)
        {
            Contract.Requires<ArgumentNullException>(target != null);
            Contract.Requires<ArgumentNullException>(execute != null);

            lock (_lock)
            {
                _activeAwareHandlers.Add(new ActiveAwareHandlers(this, target, execute, canExecute));
            }
        }

        /// <summary>
        /// Unregisters a potential target from this command.
        /// </summary>
        /// <param name="target">
        /// The target to unregister.
        /// </param>
        public void UnregisterPotentialTarget(IActiveAware target)
        {
            Contract.Requires<ArgumentNullException>(target != null);

            lock (_lock)
            {
                var activeAwareHandlers = GetHandlersForActiveAware(target);

                if (activeAwareHandlers != null)
                {
                    activeAwareHandlers.Dispose();
                    _activeAwareHandlers.Remove(activeAwareHandlers);

                    if (target == CurrentTarget)
                    {
                        CurrentTarget = null;
                    }
                }
            }
        }

        /// <summary>
        /// Determines whether this command can execute.
        /// </summary>
        /// <remarks>
        /// If the <see cref="CurrentTarget"/> is <see langword="null"/>, this method returns <see langword="false"/>. Otherwise, this method will invoke the target's
        /// can execute delegate to determine whether the command can execute. If no can execute delegate was provided, this method will return <see langword="true"/>.
        /// </remarks>
        /// <param name="parameter">
        /// The command parameter.
        /// </param>
        /// <returns>
        /// <see langword="true"/> if the command can execute, otherwise <see langword="false"/>.
        /// </returns>
        public override bool CanExecute(object parameter)
        {
            var handler = CurrentTarget;

            if (handler == null)
            {
                return false;
            }

            var activeAwareHandlers = GetHandlersForActiveAware(handler);

            if (activeAwareHandlers != null)
            {
                if (activeAwareHandlers.CanExecute == null)
                {
                    return true;
                }

                return activeAwareHandlers.CanExecute(parameter);
            }

            return false;
        }

        /// <summary>
        /// Executes this command.
        /// </summary>
        /// <remarks>
        /// This method executes the execute handler for <see cref="CurrentTarget"/>.
        /// </remarks>
        /// <param name="parameter">
        /// The command parameter.
        /// </param>
        public override void Execute(object parameter)
        {
            var handler = CurrentTarget;

            // I think this may happen if CanExecute executes, then the current target deactivates, and then WPF calls Execute
            if (handler == null)
            {
                return;
            }

            var activeAwareHandlers = GetHandlersForActiveAware(handler);

            if (activeAwareHandlers != null)
            {
                activeAwareHandlers.Execute(parameter);
            }
        }

        /// <summary>
        /// Raises the <see cref="CurrentTargetChanged"/> event.
        /// </summary>
        protected virtual void OnCurrentTargetChanged()
        {
            if (CurrentTargetChanged != null)
            {
                CurrentTargetChanged.DynamicInvoke(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets the handlers for active aware.
        /// </summary>
        /// <param name="activeAware">The active aware.</param>
        /// <returns>Active aware handler for command</returns>
        private ActiveAwareHandlers GetHandlersForActiveAware(IActiveAware activeAware)
        {
            return _activeAwareHandlers.FirstOrDefault(x => x.ActiveAware == activeAware);
        }

        /// <summary>
        /// Defines the <seealso cref="ActiveAwareHandlers"/> type.
        /// </summary>
        private sealed class ActiveAwareHandlers : IDisposable
        {
            /// <summary>
            /// Active aware command
            /// </summary>
            private readonly ActiveAwareCommand _command;

            /// <summary>
            /// weak reference to active aware
            /// </summary>
            private readonly WeakReference _activeAware;

            /// <summary>
            /// Target function to execute
            /// </summary>
            private readonly Action<object> _execute;

            /// <summary>
            /// Function to get if command can be executed
            /// </summary>
            private readonly Predicate<object> _canExecute;

            public ActiveAwareHandlers(ActiveAwareCommand command, IActiveAware activeAware, Action<object> execute, Predicate<object> canExecute)
            {
                Contract.Requires<ArgumentNullException>(command != null);
                Contract.Requires<ArgumentNullException>(activeAware != null);
                Contract.Requires<ArgumentNullException>(execute != null);

                _command = command;
                _activeAware = new WeakReference(activeAware);
                _execute = execute;
                _canExecute = canExecute;

                // if the active aware component is already active, it should become the target when registered
                if (activeAware.IsActive)
                {
                    _command.CurrentTarget = activeAware;
                }

                activeAware.IsActiveChanged += ActiveAwareIsActiveChanged;
            }

            /// <summary>
            /// Gets the active aware.
            /// </summary>
            public IActiveAware ActiveAware
            {
                get { return _activeAware.Target as IActiveAware; }
            }

            /// <summary>
            /// Gets the execute.
            /// </summary>
            public Action<object> Execute
            {
                get { return _execute; }
            }

            /// <summary>
            /// Gets the can execute.
            /// </summary>
            public Predicate<object> CanExecute
            {
                get { return _canExecute; }
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                var activeAware = ActiveAware;

                if (activeAware != null)
                {
                    activeAware.IsActiveChanged -= ActiveAwareIsActiveChanged;
                }
            }

            /// <summary>
            /// Handles the IsActiveChanged event of the _activeAware control.
            /// </summary>
            /// <param name="sender">The source of the event.</param>
            /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
            private void ActiveAwareIsActiveChanged(object sender, EventArgs e)
            {
                var activeAware = ActiveAware;

                if (activeAware != null)
                {
                    _command.CurrentTarget = activeAware.IsActive ? activeAware : null;
                }
            }
        }
    }
}